﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XSTDZ.ERP.Core.Repositories;
using XSTDZ.ERP.Core;
using System.Linq.Expressions;
using XSTDZ.ERP.Core.Specifications;

namespace XSTDZ.ERP.EntityFramework
{
    public class EntityFrameworkRepositor<TAggregateRoot> : Repository<TAggregateRoot> where TAggregateRoot : class,IAggregateRoot
    {
        private readonly IEntityFrameworkRepositorContext _efContext;
        public EntityFrameworkRepositor(IRepositoryContext context)
            : base(context)
        {
            if (context is IEntityFrameworkRepositorContext)
                this._efContext = context as IEntityFrameworkRepositorContext;
        }

        protected override void DoAdd(TAggregateRoot aggregateRoot)
        {
            this._efContext.RegisterNew(aggregateRoot);
        }

     
        protected override bool DoExists(Expression<Func<TAggregateRoot, bool>> expression)
        {
            return this._efContext.Context.Set<TAggregateRoot>().Any(expression);
        }
      

        protected override TAggregateRoot DoFindByKey(Guid key)
        {
            return this._efContext.Context.Set<TAggregateRoot>().FirstOrDefault(d => d.Id == key);
        }

        protected override void DoRemove(TAggregateRoot aggregateRoot)
        {
            this._efContext.RegisterDeleted<TAggregateRoot>(aggregateRoot);
        }

        protected override void DoUpdate(TAggregateRoot aggregateRoot)
        {
            this._efContext.RegisterModified<TAggregateRoot>(aggregateRoot);
        }


        #region private method
 

        private IQueryable<TAggregateRoot> CreateOrderByQueryable(OrderByParameter<TAggregateRoot>[] orderByParameters, IQueryable<TAggregateRoot> queryable)
        {
            var orderedQueryable = queryable as IOrderedQueryable<TAggregateRoot>;
            if (orderedQueryable == null)
                return null;
            for (int i = 0; i < orderByParameters.Length; i++)
            {
                var orderByParameter = orderByParameters[i];
                if (i == 0)
                {
                    switch (orderByParameter.SortOrder)
                    {
                        case SortOrder.Ascending:
                            orderedQueryable = orderedQueryable.OrderBy(orderByParameter.KeySelector);
                            break;
                        case SortOrder.Descending:
                            orderedQueryable = orderedQueryable.OrderByDescending(orderByParameter.KeySelector);
                            break;
                    }
                }
                else
                {
                    switch (orderByParameter.SortOrder)
                    {
                        case SortOrder.Ascending:
                            orderedQueryable = orderedQueryable.ThenBy(orderByParameter.KeySelector);
                            break;
                        case SortOrder.Descending:
                            orderedQueryable = orderedQueryable.ThenByDescending(orderByParameter.KeySelector);
                            break;
                    }
                }
            }
            queryable = orderedQueryable.AsQueryable();
            return queryable;
        }


      
        private IQueryable<TAggregateRoot> CreateEagerLoadingQueryable(Expression<Func<TAggregateRoot, bool>> expression, Expression<Func<TAggregateRoot, dynamic>>[] eagerLoadingProperties)
        {
            var dbset = _efContext.Context.Set<TAggregateRoot>();
            IQueryable<TAggregateRoot> queryable;
            if (eagerLoadingProperties != null && eagerLoadingProperties.Length > 0)
            {
                var eagerLoadingProperty = eagerLoadingProperties[0];
                var eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                var dbquery = dbset.Include(eagerLoadingPath);
                for (int i = 1; i < eagerLoadingProperties.Length; i++)
                {
                    eagerLoadingProperty = eagerLoadingProperties[i];
                    eagerLoadingPath = this.GetEagerLoadingPath(eagerLoadingProperty);
                    dbquery = dbquery.Include(eagerLoadingPath);
                }
                queryable = dbquery.Where(expression).AsQueryable();
            }
            else if(expression!=null)
                queryable = dbset.Where(expression).AsQueryable();
            else
                queryable = dbset.AsQueryable();
            return queryable;
        }


        private MemberExpression GetMemberInfo(LambdaExpression lambda)
        {
            if (lambda == null)
                throw new ArgumentNullException("lambda");

            MemberExpression memberExpr = null;

            if (lambda.Body.NodeType == ExpressionType.Convert)
            {
                memberExpr =
                    ((UnaryExpression)lambda.Body).Operand as MemberExpression;
            }
            else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
            {
                memberExpr = lambda.Body as MemberExpression;
            }

            if (memberExpr == null)
                throw new ArgumentException("method");

            return memberExpr;
        }

        private string GetEagerLoadingPath(Expression<Func<TAggregateRoot, dynamic>> eagerLoadingProperty)
        {
            MemberExpression memberExpression = this.GetMemberInfo(eagerLoadingProperty);
            var parameterName = eagerLoadingProperty.Parameters.First().Name;
            var memberExpressionStr = memberExpression.ToString();
            var path = memberExpressionStr.Replace(parameterName + ".", "");
            return path;
        }
        #endregion

        protected override TAggregateRoot DoFind(Expression<Func<TAggregateRoot, bool>> expression, params Expression<Func<TAggregateRoot, dynamic>>[] eagerLoadingProperties)
        {
            var queryable = CreateEagerLoadingQueryable(expression, eagerLoadingProperties);
            return queryable.FirstOrDefault();
        }

        protected override IEnumerable<TAggregateRoot> DoFindAll(Expression<Func<TAggregateRoot, bool>> expression, int pageNumber, int pageSize, OrderByParameter<TAggregateRoot>[] orderByParameters, params Expression<Func<TAggregateRoot, dynamic>>[] eagerLoadingProperties)
        {
            if (pageNumber <= 0)
                throw new ArgumentOutOfRangeException("pageNumber", pageNumber, @"The pageNumber is one-based and should be larger than zero.");
            if (pageSize <= 0)
                throw new ArgumentOutOfRangeException("pageSize", pageSize, @"The pageSize is one-based and should be larger than zero.");

            int skip = (pageNumber - 1) * pageSize;
            int take = pageSize;

            var queryable = CreateEagerLoadingQueryable(expression, eagerLoadingProperties);

            if (orderByParameters != null && orderByParameters.Length > 0)
            {
                queryable = CreateOrderByQueryable(orderByParameters, queryable);
            }
            return queryable.Skip(skip).Take(take).ToList();
        }

        protected override IEnumerable<TAggregateRoot> DoFindAll(Expression<Func<TAggregateRoot, bool>> expression, OrderByParameter<TAggregateRoot>[] orderByParameters, params Expression<Func<TAggregateRoot, dynamic>>[] eagerLoadingProperties)
        {
            var queryable = CreateEagerLoadingQueryable(expression, eagerLoadingProperties);

            if (orderByParameters != null && orderByParameters.Length > 0)
            {
                queryable = CreateOrderByQueryable(orderByParameters, queryable);
            }
            return queryable.ToList();
        }
    }
}
